package com.springcloud.gateway.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alicp.jetcache.Cache;
import com.alicp.jetcache.anno.CacheType;
import com.alicp.jetcache.anno.CreateCache;
import com.springcloud.gateway.common.entity.RouteDefinitionVo;
import com.springcloud.gateway.service.RouteService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * <p> redis路由服务具体实现 </p>
 *
 * @author mori
 * @since 10/25/2019 2:02 PM
 */
@Service
@Slf4j
public class RedisRouteServiceImpl implements RouteService {

    private static final String GATEWAY_ROUTES = "gateway_routes::";

    private StringRedisTemplate stringRedisTemplate;

    private Map<String, RouteDefinitionVo> routeDefinitionMaps = new HashMap<>();

    @CreateCache(name = GATEWAY_ROUTES, cacheType = CacheType.REMOTE)
    private Cache<String, RouteDefinitionVo> gatewayRouteCache = null;

    public RedisRouteServiceImpl(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @PostConstruct
    private void loadRouteDefinition() {
        log.info("loadRouteDefinition, 开始初始化路由");
        Set<String> gatewayKeys = stringRedisTemplate.keys(GATEWAY_ROUTES + "*");
        if (CollectionUtils.isEmpty(gatewayKeys)) {
            return;
        }
        log.info("预计初始化路由, gatewayKeys：{}", gatewayKeys);
        // 去掉key的前缀
        Set<String> gatewayKeyIds =
                gatewayKeys.stream()
                        .map(key -> key.replace(GATEWAY_ROUTES, StringUtils.EMPTY))
                        .collect(Collectors.toSet());
        Map<String, RouteDefinitionVo> allRoutes = gatewayRouteCache.getAll(gatewayKeyIds);
        log.info("gatewayKeys：{}", allRoutes);
        allRoutes.values().forEach(routeDefinition -> {
            try {
                routeDefinition.setUri(new URI(routeDefinition.getUri().toASCIIString()));
            } catch (URISyntaxException e) {
                log.error("网关加载RouteDefinition异常：", e);
            }
        });
        routeDefinitionMaps.putAll(allRoutes);
        log.info("共初始化路由信息：{}", routeDefinitionMaps.size());
    }

    @Override
    public Collection<RouteDefinition> getRouteDefinitions() {
        Collection<RouteDefinitionVo> routeDefinitionVoCollection = routeDefinitionMaps.values();
        return routeDefinitionVoCollection.stream()
                .map(e -> {
                    String object = JSONObject.toJSONString(e);
                    return JSON.parseObject(object, RouteDefinition.class);
                })
                .collect(Collectors.toList());
    }

    @Override
    public Mono<Void> save(Mono<RouteDefinition> routeDefinitionMono) {
        return routeDefinitionMono.flatMap(routeDefinition -> {
            RouteDefinitionVo routeDefinitionVo = JSONObject.parseObject(JSONObject.toJSONString(routeDefinition),
                    RouteDefinitionVo.class);
            routeDefinitionMaps.put(routeDefinition.getId(), routeDefinitionVo);
            gatewayRouteCache.put(routeDefinition.getId(), routeDefinitionVo);
            log.info("新增路由1条：{}", routeDefinition);
            log.info("目前路由共{}条：", routeDefinitionMaps.size());
            return Mono.empty();
        });
    }

    @Override
    public Mono<Void> delete(Mono<String> routeId) {
        return routeId.flatMap(id -> {
            routeDefinitionMaps.remove(id);
            gatewayRouteCache.remove(id);
            log.info("删除路由1条：{}", routeId);
            log.info("目前路由共{}条：", routeDefinitionMaps.size());
            return Mono.empty();
        });
    }
}
